﻿/*
 * aspark
 * Email:mwyso@126.com
 * http://quicksl.codeplex.com/
 */

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

using System.Threading;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

namespace QuickSL
{
    //load data from isolatedStorage or network
    public class BackgroundQueue : AsyncQueue<BackgroundQueueItem>,INotifyPropertyChanged
    {
        #region Singleton
        private BackgroundQueue()
            : base()
        {
            _runningThreadCollection = new Dictionary<BackgroundQueueItem, BackgroundWorker>();
        }

        private static BackgroundQueue _current = null;
        public static BackgroundQueue Current
        {
            get
            {
                if (_current == null)
                    _current = new BackgroundQueue();

                return _current;
            }
        } 
        #endregion

        private const int _MaxThreadCount = 10;
        private Dictionary<BackgroundQueueItem, BackgroundWorker> _runningThreadCollection = null;

        public event EventHandler IsBusyChanged;

        /// <summary>
        /// indicate the background is busy
        /// </summary>
        public bool IsBusy
        {
            get
            {
                return _runningThreadCollection.Count > 0 && _runningThreadCollection.All(pair => pair.Value.IsBusy);
            }
        }

        private void OnIsBusyChanged(EventArgs e)
        {
            DispatcherQueue.Current.Enqueue(() =>
            {
                NotifyPropertyChanged("IsBusy");

                if (IsBusyChanged != null)
                    IsBusyChanged(this, e);
            }, 0);
        }

        public bool GetIsBusyState(BackgroundQueueItem item)
        {
            lock (_runningThreadCollection)
            {
                if (_runningThreadCollection.ContainsKey(item))
                    return _runningThreadCollection[item].IsBusy;
            }

            return false;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="action"></param>
        /// <param name="callback"></param>
        public BackgroundQueueItem Enqueue(Action action, Action callback)
        {
            BackgroundQueueItem item = new BackgroundQueueItem(action, callback);
            base.Enqueue(item);
            return item;
        }

        /// <summary>
        /// Cancel a operation
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Cancel(BackgroundQueueItem item)
        {
            lock (_runningThreadCollection)
            {
                if (_runningThreadCollection.ContainsKey(item))
                {
                    _runningThreadCollection[item].CancelAsync();
                }
            }

            return base.Remove(item);
        }

        public override bool Remove(BackgroundQueueItem item)
        {
            return Cancel(item);
        }

        protected override void OnProcessing()
        {
            for (int i = 0; i < _MaxThreadCount; i++)
            {
                BackgroundQueueItem item = base.Dequeue();

                EnqueueImpl(item);

                if (_currentRunningThreadCount == _MaxThreadCount) //using lock?
                {
                    break; //todo:wait for thread execute completed
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        private void EnqueueImpl(BackgroundQueueItem item)
        {
            if (item != null)
            {
                BackgroundWorker worker = new BackgroundWorker();
                worker.WorkerSupportsCancellation = true;
                worker.WorkerReportsProgress = true;
                worker.DoWork += new DoWorkEventHandler(worker_DoWork);
                worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
                worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
                worker.RunWorkerAsync(item);

                BeginImplement(item, worker);
            }
        }

        private void BeginImplement(BackgroundQueueItem item, BackgroundWorker worker)
        {
            lock (_runningThreadCollection)
            {
                _runningThreadCollection.Add(item, worker);
            }
            Interlocked.Increment(ref _currentRunningThreadCount);
            OnIsBusyChanged(new EventArgs());
        }

        private void EndImplement(BackgroundQueueItem item)
        {
            lock (_runningThreadCollection)
            {
                _runningThreadCollection.Remove(item);
            }
            Interlocked.Decrement(ref _currentRunningThreadCount);
            OnIsBusyChanged(new EventArgs());

            OnProcessing();//notify pushed item to execute
        }

        void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (!e.Cancelled && e.Error == null)
            {
                BackgroundQueueItem item = e.Result as BackgroundQueueItem;
                if (item != null)
                {
                    DispatcherQueue.Current.Enqueue(item.Callback);
                }
            }

            BackgroundWorker worker = sender as BackgroundWorker;
            if (worker != null)
            {
                worker.DoWork -= new DoWorkEventHandler(worker_DoWork);
                worker.ProgressChanged -= new ProgressChangedEventHandler(worker_ProgressChanged);
                worker.RunWorkerCompleted -= new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
                worker = null;
            }
        }

        void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            
        }

        void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundQueueItem item = e.Argument as BackgroundQueueItem;
            item.Operation();

            if ((sender as BackgroundWorker).CancellationPending)
            {
                e.Cancel = true;
            }

            //(sender as BackgroundWorker).ReportProgress(100);

            e.Result = e.Argument;

            EndImplement(item);
        }

        private int _currentRunningThreadCount = 0;

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null&&!string.IsNullOrWhiteSpace(propertyName))
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
